package org.zjvis.datascience.web.audit;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.zjvis.datascience.common.dto.AuditDTO;
import org.zjvis.datascience.common.dto.user.UserDTO;
import org.zjvis.datascience.common.model.ApiResult;
import org.zjvis.datascience.common.model.ApiResultCode;
import org.zjvis.datascience.common.util.DozerUtil;
import org.zjvis.datascience.common.util.JwtUtil;
import org.zjvis.datascience.common.vo.AuditVO;
import org.zjvis.datascience.service.audit.AuditService;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @description 审计拦截切面 Aspect
 * @date 2021-09-28
 */
@Component
@Aspect
@Slf4j
public class AuditAspect {

    @Autowired
    private AuditService auditService;

    private static final Set<String> user_password_urls = new HashSet<>();

    static {
        user_password_urls.add("/auth/login");
        user_password_urls.add("/users/create");
        user_password_urls.add("/users/update");
        user_password_urls.add("/users/psdModify");
        user_password_urls.add("/sms/verify");
    }

    @Pointcut("execution(* org.zjvis..web.controller.*.*(..)))")
    public void audit() {
    }

    @AfterReturning(value = "audit()", returning = "result")
    public void sendAudit(JoinPoint joinPoint, ApiResult result) {
        if (result.getCode() != ApiResultCode.SUCCESS.getCode()) {//只对正常返回的结果进行审计
            return;
        }

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        UserDTO userDto = JwtUtil.getCurrentUserDTO();
        String url = request.getRequestURI();
        String ip = getIpAddr(request);
        Object[] args = joinPoint.getArgs();
        String params = null;
        for (Object o : args) {
            if (o == null) continue;
            if (o.getClass().toString().contains(".vo.") || o.getClass().toString().contains("JSONObject")) { //仅记录请求为VO类型或jsonObject的参数
                params = JSON.toJSONString(o);
                if (params.length() > 2047) { //参数过长，超出数据库字段长度限制，params不记录
                    params = null;
                }
                break;
            }
        }
        if (user_password_urls.contains(url)) { //部分用户相关接口中参数包含密码，params不做审计
            params = null;
        }
        String requestType = getType(url);
        String requestData = getData(params);
        String ua = request.getHeader("User-Agent");
        String referer = request.getHeader("Referer");
        Map<String, String> envInfo = new HashMap<>();
        envInfo.put("user-agent", ua);
        envInfo.put("referer", referer);
        AuditVO auditVO = new AuditVO(LocalDateTime.now(), url, params, requestType, requestData, userDto.getName(), ip, JSON.toJSONString(envInfo));

        auditService.addAuditInfo(DozerUtil.mapper(auditVO, AuditDTO.class));
    }

    /**
     * 获取用户真实IP地址，不使用request.getRemoteAddr()的原因是有可能用户使用了代理软件方式避免真实IP地址,
     * 可是，如果通过了多级反向代理的话，X-Forwarded-For的值并不止一个，而是一串IP值
     *
     * @return ip
     */
    private String getIpAddr(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
            // 多次反向代理后会有多个ip值，第一个ip才是真实ip
            if (ip.contains(",")) {
                ip = ip.split(",")[0];
            }
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

    /**
     * 根据请求参数区分请求类型
     *
     * @param url
     * @return
     */
    private String getType(String url) {
        if (url.contains("query")) {
            return "QUERY";
        } else if (url.contains("create") || url.contains("update") || url.contains("add") || url.contains("save")) {
            return "ADD";
        } else if (url.contains("delete")) {
            return "DELETE";
        } else if (url.contains("auth")) {
            return "AUTH";
        } else if (url.contains("upload") || url.contains("import")) {
            return "IMPORT";
        }
        return "OTHER";
    }

    private String getData(String params) {
        if (StringUtils.isBlank(params)) {
            return null;
        }
        String res = null;
        try {
            Map<String, Object> map = JSONObject.parseObject(params);
            if (map.containsKey("taskId")) {
                res = "taskId:" + map.get("taskId").toString();
            } else if (map.containsKey("categoryId")) {
                res = "categoryId:" + map.get("categoryId").toString();
            } else if (map.containsKey("pipelineId")) {
                res = "pipelineId:" + map.get("pipelineId").toString();
            } else if (map.containsKey("projectId")) {
                res = "projectId:" + map.get("projectId").toString();
            } else if (map.containsKey("id")) {
                res = "id:" + map.get("id").toString();
            }
        } catch (Exception ignored) {
        }
        return res;
    }
}